package com.kedacom.ctsp.authz.security.provider;

import com.kedacom.ctsp.authz.AuthenticationService;
import com.kedacom.ctsp.authz.entity.AuthUser;
import com.kedacom.ctsp.authz.security.AuthzSecurityProperties;
import com.kedacom.ctsp.authz.security.event.AuthzLoginEvent;
import com.kedacom.ctsp.authz.security.event.AuthzLogoutEvent;
import com.kedacom.ctsp.codec.CodecFactory;
import com.kedacom.ctsp.codec.aes.DESCodec;
import com.kedacom.ctsp.lang.validate.ValidationException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.web.context.request.RequestContextHolder;

import javax.annotation.PostConstruct;

import static com.kedacom.ctsp.authz.security.util.Constant.REQ_ATTR_TOKEN;
import static org.springframework.web.context.request.RequestAttributes.SCOPE_REQUEST;

/**
 * tokenManager
 *
 * @author xuwei
 * @create 2018-04-25 09:30
 **/
@Slf4j
public abstract class TokenManager {

    @Autowired
    protected AuthzSecurityProperties securityProperties;

    @Autowired
    protected ApplicationContext context;

    @Autowired
    protected AuthenticationService authenticationService;

    protected DESCodec codec;

    @PostConstruct
    public void init() {
        codec = CodecFactory.des(securityProperties.getAuthCode());
    }

    public abstract void removeAccessToken(String accessToken);

    public abstract boolean existAccessToken(String accessToken);

    public void logout(String token) {
        if (StringUtils.isNotBlank(token)) {
            String accessToken = getAccessTokenByToken(token);
            String username = getUsernameByToken(token);
            if (StringUtils.isNotBlank(accessToken)) {
                log.info("删除accessToken: " + accessToken);
                removeAccessToken(accessToken);
                log.info("发布登出事件");
                context.publishEvent(
                        new AuthzLogoutEvent(username, accessToken, token));
            }
        }

    }

    public AuthUser login(String encryptedUsername, String encryptedAccessToken) {
        String username;
        String accessToken;
        try {
            username = decrypt(encryptedUsername);
            accessToken = decrypt(encryptedAccessToken);
        } catch (Exception e) {
            throw new ValidationException("参数错误: " + e.getMessage());
        }
        return loginDirectly(username, accessToken);
    }

    public AuthUser loginDirectly(String username, String accessToken) {
        AuthUserDetails principal = new AuthUserDetails(authenticationService.loadUserByUsername(username));
        AuthUser authUser = principal.getAuthentication().getUser();

        String token = generateToken(username, accessToken);
        authUser.setToken(token);
        addAccessToken(accessToken, principal);
        SecurityContextHolder.getContext().setAuthentication(new PreAuthenticatedAuthenticationToken(principal, token, principal.getAuthorities()));
        context.publishEvent(new AuthzLoginEvent(username, accessToken, token));
        return authUser;
    }

    public AuthUser login(String username, String accessToken, String auth) {
        if (!StringUtils.equals(securityProperties.getAuthCode(), auth)) {
            throw new ValidationException("权限令牌不匹配");
        }
        return loginDirectly(username, accessToken);
    }

    public abstract void addAccessToken(String accessToken, AuthUserDetails principal);

    public abstract AuthUserDetails getAuthUserDetailsByAccessToken(String accessToken);

    private String generateToken(String username, String accessToken) {
        String encryptStr = accessToken + "@@" + username + "@@" + System.currentTimeMillis();
        String token = encrypt(encryptStr);
        // 加密生成token
        log.info("加密之后的token: [{}]", token);
        return token;
    }


    public String decrypt(String data) throws ValidationException {
        String decrypt;
        try {
            decrypt = new String(codec.decrypt(data));
        } catch (Exception e) {
            throw new ValidationException("请填写正确的数据");
        }
        return decrypt;
    }

    public String encrypt(String data) throws ValidationException {
        String encrpt;
        try {
            encrpt = new String(codec.encrypt(data));
        } catch (Exception e) {
            throw new ValidationException("请填写正确的数据");
        }
        return encrpt;
    }


    public String getAccessTokenByToken(String token) {
        String tokenDecryptStr = decrypt(token);
        if (StringUtils.isNotBlank(tokenDecryptStr)) {
            String[] sp = tokenDecryptStr.split("@@");
            if (sp != null && sp.length > 0) {
                return sp[0];
            }
        }

        return null;
    }

    public String getUsernameByToken(String token) {
        String tokenDecryptStr = decrypt(token);
        if (StringUtils.isNotBlank(tokenDecryptStr)) {
            String[] sp = tokenDecryptStr.split("@@");
            if (sp != null && sp.length > 0) {
                return sp[1];
            }
        }
        return null;
    }

    public boolean validateToken(String token) {
        String accessToken = getAccessTokenByToken(token);
        if (StringUtils.isBlank(accessToken)) {
            return false;
        }
        AuthUserDetails userDetails = getAuthUserDetailsByAccessToken(accessToken);
        if (userDetails != null) {
            return StringUtils.equals(token, userDetails.getAuthentication().getUser().getToken());
        }
        return false;
    }


    public String getCurrentSessionId() {
        return RequestContextHolder.getRequestAttributes().getSessionId();
    }

    public void loginByCurrentToken(String token) {
        if (StringUtils.isBlank(token)) {
            return;
        }
        String accessToken = getAccessTokenByToken(token);
        AuthUserDetails authUserDetails = getAuthUserDetailsByAccessToken(accessToken);
        if (authUserDetails == null) {
            return;
        }
        refreshToken(accessToken);
        SecurityContextHolder.getContext().setAuthentication(new PreAuthenticatedAuthenticationToken(authUserDetails, token, authUserDetails.getAuthorities()));
    }

    protected abstract void refreshToken(String accessToken);

    public String getCurrentToken() {
        return (String) RequestContextHolder.getRequestAttributes().getAttribute(REQ_ATTR_TOKEN, SCOPE_REQUEST);
    }
}
